// Copyright (c) 2012-2013, yuno. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
package sugarmintcandy.widgetwindow;

import java.lang.reflect.Field;

import android.annotation.SuppressLint;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

@SuppressLint("ViewConstructor")
public class WidgetWindow extends FrameLayout implements View.OnClickListener,
	View.OnLongClickListener, WindowFrameAnimation.OnFinishListener,
	ResizeView.OnResizeListener, Handler.Callback,
	SharedPreferences.OnSharedPreferenceChangeListener {
    private static final int AUTO_ADVANCE_INTERVAL = 20000;
    private static final int FRAME_ANIMATION_DURATION = 250;

    private MainDatabase mDatabase;
    private WindowInfo mInfo;
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams mLayoutParams;
    private DisplayMetrics mDisplayMetrics;
    private boolean mVisible;
    private WindowFrameAnimation mAnimation;
    private ResizeView mResizeView;
    private Handler mHandler;

    private SharedPreferences mPreferences;
    private boolean mShowTitleBarIcon;
    private boolean mShowHideTitleBarButton;
    private boolean mShowBringToFrontButton;
    private boolean mShowIconifyButton;
    private boolean mShowCloseButton;
    private boolean mAnimateIconification;
    private int mTitleBarHeight;

    private ImageView mIconView;
    private View mNormalView;
    private View mTitleBar;
    private ImageView mTitleBarIcon;
    private TextView mTitleBarText;
    private ImageView mHideTitleBarButton;
    private ImageView mBringToFrontButton;
    private ImageView mIconifyButton;
    private ImageView mCloseButton;
    private FrameLayout mContentFrame;
    private MyAppWidgetHostView mContentView;
    private View mAutoAdvanceView;

    public WidgetWindow(Context context, MainDatabase database,
	    MyAppWidgetHostView contentView, WindowInfo info) {
	super(context);

	mDatabase = database;
	mWindowManager = (WindowManager) context
		.getSystemService(Context.WINDOW_SERVICE);
	mInfo = info;
	mLayoutParams = new WindowManager.LayoutParams();
	mHandler = new Handler(this);
	mDisplayMetrics = Utilities.getDisplayMetrics(mWindowManager);

	mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
	mPreferences.registerOnSharedPreferenceChangeListener(this);
	fetchPreferences();

	LayoutInflater inflater = LayoutInflater.from(context);
	inflater.inflate(R.layout.widget_window, this);

	mIconView = (ImageView) findViewById(R.id.icon_view);
	mIconView.setImageDrawable(info.loadIcon(context));
	mIconView.setOnTouchListener(new IconViewTouchListener());

	mNormalView = findViewById(R.id.normal_view);

	mTitleBar = findViewById(R.id.title_bar);
	mTitleBar.setOnTouchListener(new TitleBarTouchListener());

	mTitleBarIcon = (ImageView) findViewById(R.id.title_icon);
	mTitleBarIcon.setImageDrawable(info.loadIcon(context));

	mTitleBarText = (TextView) findViewById(R.id.title_text);

	mHideTitleBarButton = (ImageView) findViewById(R.id.hide_title_bar_button);
	mHideTitleBarButton.setOnClickListener(this);
	mHideTitleBarButton.setImageDrawable(new HideTitleBarButtonDrawable());

	mBringToFrontButton = (ImageView) findViewById(R.id.bring_to_front_button);
	mBringToFrontButton.setOnClickListener(this);
	mBringToFrontButton.setImageDrawable(new BringToFrontButtonDrawable());

	mIconifyButton = (ImageView) findViewById(R.id.iconify_button);
	mIconifyButton.setOnClickListener(this);
	mIconifyButton.setImageDrawable(new IconifyButtonDrawable());

	mCloseButton = (ImageView) findViewById(R.id.close_button);
	mCloseButton.setOnClickListener(this);
	mCloseButton.setImageDrawable(new CloseButtonDrawable());

	mContentFrame = (FrameLayout) findViewById(R.id.content_frame);
	mContentFrame.addView(contentView, new FrameLayout.LayoutParams(
		ViewGroup.LayoutParams.MATCH_PARENT,
		ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
	mContentView = contentView;
	mContentView.setOnLongClickListener(this);

	updateWindow();
    }

    public void destroy() {
	hide();
	mContentFrame.removeView(mContentView);
	mContentView = null;
	mAutoAdvanceView = null;
	mPreferences.unregisterOnSharedPreferenceChangeListener(this);
    }

    private void fetchPreferences() {
	mShowTitleBarIcon = mPreferences.getBoolean(
		PreferenceKeys.SHOW_TITLE_BAR_ICON, false);
	mShowHideTitleBarButton = mPreferences.getBoolean(
		PreferenceKeys.SHOW_HIDE_TITLE_BAR_BUTTON, false);
	mShowBringToFrontButton = mPreferences.getBoolean(
		PreferenceKeys.SHOW_BRING_TO_FRONT_BUTTON, false);
	mShowIconifyButton = mPreferences.getBoolean(
		PreferenceKeys.SHOW_ICONIFY_BUTTON, false);
	mShowCloseButton = mPreferences.getBoolean(
		PreferenceKeys.SHOW_CLOSE_BUTTON, false);
	mAnimateIconification = mPreferences.getBoolean(
		PreferenceKeys.ANIMATE_ICONIFICATION, false);
	String titleBarSize = mPreferences.getString(
		PreferenceKeys.TITLE_BAR_SIZE, null);
	mTitleBarHeight = Math.round(getResources().getDisplayMetrics().density
		* Integer.parseInt(titleBarSize));
    }

    public int getAppWidgetId() {
	return mInfo.appWidgetId;
    }

    public MyAppWidgetHostView getAppWidgetHostView() {
	return mContentView;
    }

    private void updateWindowLater(long delay) {
	mHandler.removeMessages(R.id.update_window);
	mHandler.sendEmptyMessageDelayed(R.id.update_window, delay);
    }

    public void updateWindow(WindowInfo info) {
	mInfo = info;
	updateWindow();
    }

    public void updateWindow() {
	stopFrameAnimation();
	mHandler.removeMessages(R.id.update_window);

	DisplayMetrics dm = mDisplayMetrics;
	Utilities.getDisplayMetrics(mWindowManager, dm);
	WindowManager.LayoutParams params = mLayoutParams;
	params.packageName = getContext().getPackageName();
	params.alpha = 1;
	params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
	params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
		| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
		| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
		| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
	params.gravity = Gravity.TOP | Gravity.LEFT;
	params.format = PixelFormat.TRANSLUCENT;
	Rect bounds;
	if (mInfo.iconified) {
	    mIconView.setVisibility(VISIBLE);
	    mNormalView.setVisibility(GONE);
	    bounds = getIconifiedBounds();
	} else {
	    mIconView.setVisibility(GONE);
	    mNormalView.setVisibility(VISIBLE);
	    bounds = getWindowBounds();
	}
	params.x = bounds.left;
	params.y = bounds.top;
	params.width = bounds.right - bounds.left;
	params.height = bounds.bottom - bounds.top;
	params.windowAnimations = android.R.style.Animation_Dialog;
	if (mResizeView != null) {
	    mResizeView.setBounds(bounds);
	}

	mIconView.setBackgroundColor(mInfo.windowColor);
	mContentFrame.setBackgroundColor(mInfo.windowColor);
	mTitleBar.setVisibility(mInfo.titleBarVisible ? VISIBLE : GONE);
	mTitleBar.setBackgroundColor(mInfo.titleBarColor);
	mTitleBar.getLayoutParams().height = mTitleBarHeight;
	mTitleBar.getParent().requestLayout();
	mTitleBarIcon.setVisibility(mShowTitleBarIcon ? VISIBLE : GONE);
	mTitleBarText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
		mTitleBarHeight * 2f / 3f);
	mTitleBarText.setText(mInfo.getTitleBarText());
	mHideTitleBarButton.setVisibility(mShowHideTitleBarButton ? VISIBLE
		: GONE);
	mBringToFrontButton.setVisibility(mShowBringToFrontButton ? VISIBLE
		: GONE);
	mIconifyButton.setVisibility(mShowIconifyButton ? VISIBLE : GONE);
	mCloseButton.setVisibility(mShowCloseButton ? VISIBLE : GONE);

	if (mVisible) {
	    mWindowManager.updateViewLayout(this, mLayoutParams);
	}
    }

    @Override
    public boolean handleMessage(Message msg) {
	int what = msg.what;
	if (what == R.id.auto_advance) {
	    if (doAutoAdvance()) {
		mHandler.sendEmptyMessageDelayed(R.id.auto_advance,
			AUTO_ADVANCE_INTERVAL);
	    }
	}
	if (what == R.id.update_window) {
	    updateWindow();
	}
	return true;
    }

    public void show() {
	if (!mVisible) {
	    mWindowManager.addView(this, mLayoutParams);
	    initAutoAdvance();
	    mVisible = true;
	}
    }

    public void hide() {
	if (mVisible) {
	    stopResizeWindow();
	    stopFrameAnimation();
	    mWindowManager.updateViewLayout(this, mLayoutParams);
	    mWindowManager.removeView(this);
	    mHandler.removeMessages(R.id.auto_advance);
	    mVisible = false;
	}
    }

    public void iconify() {
	mInfo.iconified = true;
	mDatabase.updateWindowInfo(mInfo, false);
	mIconView.setVisibility(GONE);
	mNormalView.setVisibility(GONE);
	if (mAnimateIconification) {
	    Rect windowBounds = getWindowBounds();
	    Rect iconifiedBounds = getIconifiedBounds();
	    startFrameAnimation(windowBounds, iconifiedBounds);
	} else {
	    updateWindowLater(0);
	}
	if (ApiHelper.SDK_INT >= 16) {
	    Rect iconifiedBounds = getIconifiedBounds();
	    mLayoutParams.x = iconifiedBounds.left;
	    mLayoutParams.y = iconifiedBounds.top;
	    mLayoutParams.width = iconifiedBounds.right - iconifiedBounds.left;
	    mLayoutParams.height = iconifiedBounds.bottom - iconifiedBounds.top;
	    mLayoutParams.alpha = 0;
	    mWindowManager.updateViewLayout(WidgetWindow.this, mLayoutParams);
	    updateWindowLater(500);
	}
    }

    public void uniconify() {
	mInfo.iconified = false;
	mDatabase.updateWindowInfo(mInfo, false);
	mIconView.setVisibility(GONE);
	mNormalView.setVisibility(GONE);
	if (mAnimateIconification) {
	    Rect windowBounds = getWindowBounds();
	    Rect iconifiedBounds = getIconifiedBounds();
	    startFrameAnimation(iconifiedBounds, windowBounds);
	} else {
	    updateWindowLater(0);
	}
	if (ApiHelper.SDK_INT >= 16) {
	    Rect windowBounds = getWindowBounds();
	    mLayoutParams.x = windowBounds.left;
	    mLayoutParams.y = windowBounds.top;
	    mLayoutParams.width = windowBounds.right - windowBounds.left;
	    mLayoutParams.height = windowBounds.bottom - windowBounds.top;
	    mLayoutParams.alpha = 0;
	    mWindowManager.updateViewLayout(WidgetWindow.this, mLayoutParams);
	    updateWindowLater(500);
	}
    }

    private void startFrameAnimation(Rect from, Rect to) {
	stopFrameAnimation();
	mAnimation = new WindowFrameAnimation(getContext());
	mAnimation.start(from, to, FRAME_ANIMATION_DURATION, mInfo.windowColor,
		this);
    }

    private void stopFrameAnimation() {
	if (mAnimation != null) {
	    mAnimation.stop();
	    mAnimation = null;
	}
    }

    @Override
    public void onFinish(WindowFrameAnimation animation) {
	mAnimation = null;
	if (!mHandler.hasMessages(R.id.update_window)) {
	    updateWindow();
	}
    }

    @Override
    public void onClick(View v) {
	if (v == mHideTitleBarButton) {
	    mInfo.titleBarVisible = false;
	    mDatabase.updateWindowInfo(mInfo, true);
	}
	if (v == mBringToFrontButton) {
	    mInfo.zOrder = mDatabase.getMaxZOrder() + 1;
	    mDatabase.updateWindowInfo(mInfo, true);
	}
	if (v == mIconifyButton) {
	    iconify();
	}
	if (v == mCloseButton) {
	    int currentWorkspace = mPreferences.getInt(
		    PreferenceKeys.CURRENT_WORKSPACE, 0);
	    mInfo.workspace &= ~(1 << currentWorkspace);
	    mDatabase.updateWindowInfo(mInfo, true);
	}
    }

    @Override
    public boolean onLongClick(View v) {
	if (!mInfo.titleBarVisible) {
	    mInfo.titleBarVisible = true;
	    mDatabase.updateWindowInfo(mInfo, true);
	    return true;
	} else {
	    return false;
	}
    }

    private Rect getIconifiedBounds() {
	int iconSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
	int x = mInfo.iconX;
	int y = mInfo.iconY;
	Rect r;
	if (x == Integer.MIN_VALUE || y == Integer.MIN_VALUE) {
	    r = findIconSpace();
	    Point p = mInfo.anchor.encodePoint(mDisplayMetrics, r.left, r.top,
		    r.right, r.bottom);
	    mInfo.iconX = x = p.x;
	    mInfo.iconY = y = p.y;
	    mDatabase.updateWindowInfo(mInfo, false);
	} else {
	    r = mInfo.anchor.decodeRect(mDisplayMetrics, x, y, iconSize,
		    iconSize);
	}
	limitBounds(r, iconSize);
	return r;
    }

    private Rect getWindowBounds() {
	int width = mInfo.windowWidth;
	int height = mInfo.windowHeight;
	if (mInfo.titleBarVisible) {
	    height += mTitleBarHeight;
	}
	int x = mInfo.windowX;
	int y = mInfo.windowY;
	Rect r = mInfo.anchor.decodeRect(mDisplayMetrics, x, y, width, height);
	limitBounds(r, Math.round(16 * mDisplayMetrics.density));
	return r;
    }

    private void setIconifiedPosition(int left, int top) {
	int iconSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
	Rect r = new Rect(left, top, left + iconSize, top + iconSize);
	limitBounds(r, iconSize);
	Point p = mInfo.anchor.encodePoint(mDisplayMetrics, r.left, r.top,
		r.right, r.bottom);
	mInfo.iconX = p.x;
	mInfo.iconY = p.y;
	if (mInfo.iconified) {
	    mLayoutParams.x = r.left;
	    mLayoutParams.y = r.top;
	    mLayoutParams.width = iconSize;
	    mLayoutParams.height = iconSize;
	    if (mVisible) {
		mWindowManager.updateViewLayout(this, mLayoutParams);
	    }
	}
    }

    private void setWindowBounds(int left, int top, int right, int bottom) {
	Rect r = new Rect(left, top, right, bottom);
	limitBounds(r, Math.round(16 * mDisplayMetrics.density));
	Point p = mInfo.anchor.encodePoint(mDisplayMetrics, r.left, r.top,
		r.right, r.bottom);
	mInfo.windowX = p.x;
	mInfo.windowY = p.y;
	mInfo.windowWidth = r.width();
	mInfo.windowHeight = r.height();
	if (mInfo.titleBarVisible) {
	    mInfo.windowHeight -= mTitleBarHeight;
	}
	if (!mInfo.iconified) {
	    mLayoutParams.x = r.left;
	    mLayoutParams.y = r.top;
	    mLayoutParams.width = r.width();
	    mLayoutParams.height = r.height();
	    if (mVisible) {
		mWindowManager.updateViewLayout(this, mLayoutParams);
	    }
	    if (mResizeView != null) {
		mResizeView.setBounds(r);
	    }
	}
    }

    private void limitBounds(Rect r, int limit) {
	int leftLimit = limit - r.width();
	int topLimit = 0;
	int rightLimit = mDisplayMetrics.widthPixels + r.width() - limit;
	int bottomLimit = mDisplayMetrics.heightPixels + r.height() - limit;
	if (r.left < leftLimit) {
	    int dx = leftLimit - r.left;
	    r.left += dx;
	    r.right += dx;
	}
	if (r.right > rightLimit) {
	    int dx = rightLimit - r.right;
	    r.left += dx;
	    r.right += dx;
	}
	if (r.top < topLimit) {
	    int dy = topLimit - r.top;
	    r.top += dy;
	    r.bottom += dy;
	}
	if (r.bottom > bottomLimit) {
	    int dy = bottomLimit - r.bottom;
	    r.top += dy;
	    r.bottom += dy;
	}
    }

    private Rect findIconSpace() {
	MainDatabase db = mDatabase;
	DisplayMetrics dm = mDisplayMetrics;
	WindowInfo[] infoList = db.getWindowInfoList();
	Context context = db.getContext();
	int iconSize = context.getResources().getDimensionPixelSize(
		R.dimen.icon_size);
	int bottomMargin = 0;
	if (ApiHelper.SDK_INT >= 11) {
	    bottomMargin = Math.round(48 * dm.density);
	}
	int spaceLeft, spaceTop, spaceWidth, spaceHeight;
	switch (mInfo.anchor) {
	case UPPER_LEFT:
	    spaceWidth = Math.min(dm.widthPixels, dm.heightPixels);
	    spaceHeight = spaceWidth - bottomMargin;
	    spaceLeft = spaceTop = 0;
	    break;
	case UPPER_RIGHT:
	    spaceWidth = Math.min(dm.widthPixels, dm.heightPixels);
	    spaceHeight = spaceWidth - bottomMargin;
	    spaceLeft = dm.widthPixels - spaceWidth;
	    spaceTop = 0;
	    break;
	case LOWER_LEFT:
	    spaceWidth = Math.min(dm.widthPixels, dm.heightPixels);
	    spaceHeight = spaceWidth - bottomMargin;
	    spaceLeft = 0;
	    spaceTop = dm.heightPixels - spaceHeight - bottomMargin;
	    break;
	case LOWER_RIGHT:
	    spaceWidth = Math.min(dm.widthPixels, dm.heightPixels);
	    spaceHeight = spaceWidth - bottomMargin;
	    spaceLeft = dm.widthPixels - spaceWidth;
	    spaceTop = dm.heightPixels - spaceHeight - bottomMargin;
	    break;
	default:
	    spaceLeft = spaceTop = 0;
	    spaceWidth = dm.widthPixels;
	    spaceHeight = dm.heightPixels - bottomMargin;
	    break;
	}
	int colCount = spaceWidth / (iconSize * 9 / 8);
	int rowCount = spaceHeight / (iconSize * 9 / 8);
	for (int retryCount = 0;; retryCount++) {
	    for (int row = rowCount - 1; row >= 0; row--) {
		for (int col = 0; col < colCount; col++) {
		    int left = spaceLeft + col * spaceWidth / colCount;
		    int right = spaceLeft + (col + 1) * spaceWidth / colCount;
		    int top = spaceTop + row * spaceHeight / rowCount;
		    int bottom = spaceTop + (row + 1) * spaceHeight / rowCount;
		    int conflictCount = 0;
		    for (WindowInfo info : infoList) {
			Rect r = info.anchor.decodeRect(dm, info.iconX,
				info.iconY, iconSize, iconSize);
			if (left < r.right && top < r.bottom && right > r.left
				&& bottom > r.top) {
			    conflictCount++;
			}
		    }
		    if (conflictCount <= retryCount) {
			Rect r = new Rect();
			r.left = (left + right - iconSize) / 2;
			r.top = (top + bottom - iconSize) / 2;
			r.right = r.left + iconSize;
			r.bottom = r.top + iconSize;
			return r;
		    }
		}
	    }
	}
    }

    private void startResizeWindow() {
	stopFrameAnimation();
	if (mResizeView == null) {
	    mResizeView = new ResizeView(getContext());
	    mResizeView.setOnResizeListener(this);
	    mResizeView.setBounds(getWindowBounds());
	    mResizeView.show();
	}
    }

    private void stopResizeWindow() {
	if (mResizeView != null) {
	    mResizeView.hide();
	    mResizeView = null;
	}
    }

    @Override
    public void onSizeChanged(Rect bounds) {
	setWindowBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
    }

    @Override
    public void onResizeFinished() {
	stopResizeWindow();
	mDatabase.updateWindowInfo(mInfo, false);
    }

    public void initAutoAdvance() {
	AppWidgetProviderInfo providerInfo = mInfo.providerInfo;
	if (providerInfo != null) {
	    int id = ApiHelper.getAutoAdvanceViewId(providerInfo);
	    View view = mContentView.findViewById(id);
	    if (ApiHelper.isAdvanceable(view)) {
		try {
		    ApiHelper.fyiWillBeAdvancedByHostKThx(view);
		    mAutoAdvanceView = view;
		    mHandler.sendEmptyMessageDelayed(R.id.auto_advance,
			    Utilities.randomInt(AUTO_ADVANCE_INTERVAL)
				    + AUTO_ADVANCE_INTERVAL / 2);
		} catch (Throwable ex) {
		    Debug.warn(ex);
		}
	    }
	}
    }

    public boolean doAutoAdvance() {
	AppWidgetProviderInfo providerInfo = mInfo.providerInfo;
	if (providerInfo != null) {
	    int id = ApiHelper.getAutoAdvanceViewId(providerInfo);
	    View view = mContentView.findViewById(id);
	    if (ApiHelper.isAdvanceable(view)) {
		try {
		    if (view != mAutoAdvanceView) {
			ApiHelper.fyiWillBeAdvancedByHostKThx(view);
			mAutoAdvanceView = view;
		    }
		    ApiHelper.advance(view);
		    return true;
		} catch (Throwable ex) {
		    Debug.warn(ex);
		}
	    }
	}
	return false;
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
	if (PreferenceKeys.SHOW_TITLE_BAR_ICON.equals(key)
		|| PreferenceKeys.SHOW_HIDE_TITLE_BAR_BUTTON.equals(key)
		|| PreferenceKeys.SHOW_BRING_TO_FRONT_BUTTON.equals(key)
		|| PreferenceKeys.SHOW_ICONIFY_BUTTON.equals(key)
		|| PreferenceKeys.SHOW_CLOSE_BUTTON.equals(key)
		|| PreferenceKeys.ANIMATE_ICONIFICATION.equals(key)
		|| PreferenceKeys.TITLE_BAR_SIZE.equals(key)) {
	    fetchPreferences();
	    updateWindowLater(0);
	}
    }

    @Override
    protected void onAttachedToWindow() {
	super.onAttachedToWindow();
	setHasWindowFocus(true);
    }

    private void setHasWindowFocus(boolean b) {
	try {
	    Field mAttachInfo = View.class.getDeclaredField("mAttachInfo");
	    mAttachInfo.setAccessible(true);
	    Object attachInfo = mAttachInfo.get(this);
	    if (attachInfo != null) {
		Field mHasWindowFocus = attachInfo.getClass().getDeclaredField(
			"mHasWindowFocus");
		mHasWindowFocus.setAccessible(true);
		mHasWindowFocus.setBoolean(attachInfo, b);
	    }
	} catch (Throwable ex) {
	    Debug.warn(ex);
	}
    }

    private class TitleBarTouchListener implements OnTouchListener,
	    GestureDetector.OnGestureListener {
	private GestureDetector mGestureDetector;
	private int mStartX;
	private int mStartY;
	private float mDeltaX;
	private float mDeltaY;
	private boolean mChanged;

	TitleBarTouchListener() {
	    mGestureDetector = new GestureDetector(getContext(), this);
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
	    float dx = event.getRawX() - event.getX();
	    float dy = event.getRawY() - event.getY();
	    event.offsetLocation(dx, dy);
	    mGestureDetector.onTouchEvent(event);
	    int action = event.getAction();
	    if (action == MotionEvent.ACTION_CANCEL
		    || action == MotionEvent.ACTION_UP) {
		if (mChanged) {
		    mDatabase.updateWindowInfo(mInfo, false);
		    mChanged = false;
		}
	    }
	    return true;
	}

	@Override
	public boolean onDown(MotionEvent e) {
	    mStartX = mLayoutParams.x;
	    mStartY = mLayoutParams.y;
	    mDeltaX = 0;
	    mDeltaY = 0;
	    return true;
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
		float velocityY) {
	    return false;
	}

	@Override
	public void onLongPress(MotionEvent e) {
	    startResizeWindow();
	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2,
		float distanceX, float distanceY) {
	    mDeltaX += distanceX;
	    mDeltaY += distanceY;
	    int x = Math.round(mStartX - mDeltaX);
	    int y = Math.round(mStartY - mDeltaY);
	    setWindowBounds(x, y, x + mLayoutParams.width, y
		    + mLayoutParams.height);
	    mChanged = true;
	    return true;
	}

	@Override
	public void onShowPress(MotionEvent e) {
	}

	@Override
	public boolean onSingleTapUp(MotionEvent e) {
	    return false;
	}
    }

    private class IconViewTouchListener implements OnTouchListener,
	    GestureDetector.OnGestureListener {
	private GestureDetector mGestureDetector;
	private int mStartX;
	private int mStartY;
	private float mDeltaX;
	private float mDeltaY;
	private boolean mChanged;

	public IconViewTouchListener() {
	    mGestureDetector = new GestureDetector(getContext(), this);
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
	    float dx = event.getRawX() - event.getX();
	    float dy = event.getRawY() - event.getY();
	    event.offsetLocation(dx, dy);
	    mGestureDetector.onTouchEvent(event);
	    int action = event.getAction();
	    if (action == MotionEvent.ACTION_CANCEL
		    || action == MotionEvent.ACTION_UP) {
		if (mChanged) {
		    mDatabase.updateWindowInfo(mInfo, false);
		    mChanged = false;
		}
	    }
	    return true;
	}

	@Override
	public boolean onDown(MotionEvent e) {
	    mStartX = mLayoutParams.x;
	    mStartY = mLayoutParams.y;
	    mDeltaX = 0;
	    mDeltaY = 0;
	    return true;
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
		float velocityY) {
	    return false;
	}

	@Override
	public void onLongPress(MotionEvent e) {
	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2,
		float distanceX, float distanceY) {
	    mDeltaX += distanceX;
	    mDeltaY += distanceY;
	    int x = Math.round(mStartX - mDeltaX);
	    int y = Math.round(mStartY - mDeltaY);
	    setIconifiedPosition(x, y);
	    mChanged = true;
	    return true;
	}

	@Override
	public void onShowPress(MotionEvent e) {
	}

	@Override
	public boolean onSingleTapUp(MotionEvent e) {
	    uniconify();
	    return true;
	}
    }
}
